﻿<h1>代码包和包引入</h1>

<p>
和很多现代编程语言一样，Go代码包（package）来组织管理代码。
我们必须先引入一个代码包（除了<code>builtin</code>标准库包）才能使用其中导出的资源（比如函数、类型、变量和有名常量等）。
此篇文章将讲解Go代码包和代码包引入（import）。
</p>

<a class="anchor" id="import"></a>
<h3>包引入</h3>

<div>

下面这个简短的程序（假设它存在一个名为<code>simple-import-demo.go</code>的源文件中）引入了一个标准库包。

<pre class="line-numbers must-line-numbers"><code class="language-go">package main

import "fmt"

func main() {
	fmt.Println("Go has", 25, "keywords.")
}
</code></pre>

对此程序的一些解释：
<ul>
<li>
	第一行指定了源文件<code>simple-import-demo.go</code>所处的包名为<code>main</code>。
	程序入口<code>main</code>函数必须处于一个名为<code>main</code>的代码包中。
</li>
<li>
	第三行通过使用<code>import</code>关键字引入了<code>fmt</code>标准库包。
	在此源文件中，<code>fmt</code>标准库包将用<code>fmt</code>标识符来表示。
	标识符<code>fmt</code>称为<code>fmt</code>标准库包的引入名称。（后续某节将详述代码包的引入名称）。
</li>
<li>
	<code>fmt</code>标准库包中声明了很多终端打印函数供其它代码包使用。
	<code>Println</code>函数是其中之一。
	它可以将不定数量参数的字符串表示形式输出到标准输出中。
	第六行调用了此<code>Println</code>函数。
	注意在此调用中，函数名之前需要带上前缀<code>fmt.</code>，其中<code>fmt</code>是<code>Println</code>函数所处的代码包的引入名称。
	<code>aImportName.AnExportedIdentifier</code>这种形式称为一个限定标识符（<a href="https://golang.google.cn/ref/spec#Qualified_identifiers">qualified identifier</a>）。
</li>
<li>
	<code>fmt.Println</code>函数调用接受任意数量的实参并且对实参的类型没有任何限制。
	所以此程序中的此函数调用的三个实参的类型将被推断为它们各自的默认类型：<code>string</code>、<code>int</code>和<code>string</code>。
</li>
<li>
	对于一个<code>fmt.Println</code>函数调用，任何两个相邻的实参的输出之间将被插入一个空格字符，并且在最后将输出一个空行字符。
</li>
</ul>

下面是上面这个程序的运行结果：

<pre class="output"><code>$ go run simple-import-demo.go
Go has 25 keywords.
</code></pre>

<p>
当一个代码包被引入一个Go源文件时，只有此代码包中的<a href="keywords-and-identifiers.html#identifier">导出</a>资源（名称为大写字母的变量、常量、函数、定义类型和类型别名等）可以在此源文件被使用。
比如上例中的<code>Println</code>函数即为一个导出资源，所以它可以在上面的程序源文件中使用。
</p>

<p>
前面几篇文章中使用的内置函数<code>print</code>和<code>println</code>提供了和<code>fmt</code>标准库包中的对应函数相似的功能。
内置函数可以不用引入任何代码包而直接使用。
</p>

<p>
注意：<code>print</code>和<code>println</code>这两个内置函数不推荐使用在生产环境，因为它们不保证一定会出现在以后的Go版本中。
</p>

<p>
我们可以访问<a href="https://golang.org/pkg/">Go官网</a>（<a href="https://golang.google.cn/pkg/">墙内版</a>）来查看各个标准库包的文档，
我们也可以<a href="go-sdk.html#doc">开启一个本地文档服务器</a>来查看这些文档。
</p>

<p>
一个包引入也可称为一个包声明。一个包声明只在当前包含此声明的源文件内可见。
</p>

另外一个例子：
<pre class="line-numbers"><code class="language-go">package main

import "fmt"
import "math/rand"

func main() {
	fmt.Printf("下一个伪随机数总是%v。\n", rand.Uint32())
}
</code></pre>

<p>
这个例子多引入了一个<code>math/rand</code>标准库包。
此包是<code>math</code>标准库包中的一个子包。
此包提供了一些函数来产生伪随机数序列。
</p>

一些解释：
<ul>
<li>
	在此例中，<code>math/rand</code>标准库包的引入名是<code>rand</code>。
	<code>rand.Uint32()</code>函数调用将返回一个<code>uint32</code>类型的随机数。
</li>
<li>
	<code>Printf</code>函数是<code>fmt</code>标准库包中提供的另外一个常用终端打印函数。
	一个<code>Printf</code>函数调用必须带有至少一个实参，并且第一个实参的类型必须为<code>string</code>。
	此第一个实参指定了此调用的打印格式。此格式中的<code>%v</code>在打印结果将被对应的后续实参的字符串表示形式所取代。
	比如上列中的<code>%v</code>在打印结果中将被<code>rand.Uint32()</code>函数调用所返回的随机数所取代。
	打印格式中的<code>\n</code>表示一个换行符，这在<a href="basic-types-and-value-literals.html">基本类型和它们的字面表示形式</a>一文中已经解释过。
</li>
</ul>

上面这个程序的输出如下：
<pre class="output"><code>下一个伪随机数总是2596996162。
</code></pre>

<p>
如果我们希望上面的程序每次运行的时候输出一个不同的随机数，我们需要在程序启动的时候使用调用<code>rand.Seed</code>函数来设置一个不同的随机数种子。
</p>

<p>
多个包引入语句可以用一对小括号来合并成一个包引入语句。比如下面这例。
</p>

<pre class="line-numbers"><code class="language-go">package main

// 一条包引入语句引入了三个代码包。
import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	rand.Seed(time.Now().UnixNano()) // 设置随机数种子
	fmt.Printf("下一个伪随机数总是%v。\n", rand.Uint32())
}
</code></pre>

一些解释：
<ul>
<li>
	此例多引入了一个<code>time</code>标准库包。
	此包提供了很多和时间相关的函数和类型。
	其中<code>time.Time</code>和<code>time.Duration</code>是两个最常用的类型。
</li>
<li>
	函数调用<code>time.Now()</code>将返回一个表示当前时间的类型为<code>time.Time</code>的值。
</li>
<li>
	<code>UnixNano</code>是类型<code>time.Time</code>的一个方法。
	我们可以把方法看作是特殊的函数。方法将在<a href="method.html">Go中的方法</a>一文中详述。
	方法调用<code>aTime.UnixNano()</code>将返回从UTC时间的1970年一月一日到<code>aTime</code>所表示的时间之间的纳秒数。
	返回结果的类型为<code>int64</code>。
	在上例中，此方法调用的结果用来设置随机数种子。
</li>
</ul>

</div>

<h3>更多关于<code>fmt.Printf</code>函数调用的输出格式</h3>

<div>
从上面的例子中，我们已经了解到<code>fmt.Printf</code>函数调用的第一个实参中的<code>%v</code>在输出中将替换为后续的实参的字符串表示形式。
实际上，这种百分号开头的占位字符组合还有很多。下面是一些常用的占位字符组合：

<ul>
<li>
	<code>%v</code>：将被替换为对应实参字符串表示形式。
</li>
<li>
	<code>%T</code>：将替换为对应实参的类型的字符串表示形式。
</li>
<li>
	<code>%x</code>：将替换为对应实参的十六进制表示。实参的类型必须为整数，整数数组（array）或者整数切片（slice）等。
	（数组和切片将在以后的文章中讲解。）
</li>
<li>
	<code>%s</code>：将被替换为对应实参的字符串表示形式。实参的类型必须为字符串或者字节切片（byte slice）类型。
</li>
<li>
	<code>%%</code>：将被替换为一个百分号。
</li>
</ul>

一个例子：
<pre class="line-numbers"><code class="language-go">package main

import "fmt"

func main() {
	a, b := 123, "Go"
	fmt.Printf("a == %v == 0x%x, b == %s\n", a, a, b)
	fmt.Printf("type of a: %T, type of b: %T\n", a, b)
}
</code></pre>

输出：
<pre class="output"><code>a == 123 == 0x7b, b == Go
type of a: int, type of b: string
1% 50% 99%
</code></pre>

<p>
</p>

<p>
请阅读<a href="https://golang.google.cn/pkg/fmt/"><code>fmt</code>标准库包的文档</a>以了解更多的占位字符组合。
我们也可以运行<code>go doc fmt</code>命令来在终端中查看<code>fmt</code>标准库包的文档。
运行<code>go doc fmt.Printf</code>命令可以查看<code>fmt.Printf</code>函数的文档。
</p>
</div>

<h3>代码包文件夹、代码包引入路径、和代码包依赖关系</h3>

<div>
<p>
一个代码包可以由若干Go源文件组成。一个代码包的源文件须都处于同一个文件夹下。
一个文件夹（不包含子文件夹）下的所有源文件必须都处于同一个代码包中，亦即这些源文件开头的<code>package pkgname</code>语句必须一致。
所以，一个代码包对应着一个文件夹（不包含子文件夹），反之亦然。
对应着一个代码包的文件夹称为此代码包的文件夹。
一个代码包文件夹下的每个子文件夹对应的都是另外一个独立的代码包。
</p>

<p>
根据不同的情形，名称为<code>vendor</code>的文件夹可能被视为特殊的文件夹。下面的段落将会解释其特殊性。
</p>

Go SDK 1.11版本引入了包模块（module）的概念。
此概念对代码包的引入路径产生了一些影响。
一个Go代码包可以选择是否支持模块模式。
Go SDK 1.11引入了一个环境变量<code>GO111MODULE</code>。这个环境变量的默认值是<code>auto</code>，它的其它两个可能取值为<code>on</code>和<code>off</code>。
如果此环境变量取值为<code>on</code>，则所有的代码包都支持模块模式；
如果此环境变量取值为<code>off</code>，则所有的代码包都不支持模块模式；
如果此环境变量取值为<code>auto</code>，则只有同时满足下面两个条件的代码包才支持模块模式：
<ol>
<li>
	代码包文件夹必须处于<code>GOPATH</code>环境变量指定的所有路径的<code>src</code>子目录以外。
</li>
<li>
	代码包文件夹或者它的某个上级目录文件夹中含有一个名为<code>go.mod</code>文件。
</li>
</ol>

<p>
对于一个不支持模块模式的代码包，通常它应该处于某个<code>GOPATH/src</code>路径之下。
它的包引入路径为此包的文件夹相对于<code>GOPATH/src</code>或者某个<code>vendor</code>目录的相对路径。
</p>

在不支持模块模式的情形下，假设有一个如下的包层级结构，则：
<ul>
<li>
	两个<code>foo</code>包的引入路径均为<code>w/foo</code>。
</li>
<li>
	<code>x</code>包、<code>y</code>包和<code>z</code>包的引入路径均分别为<code>x</code>、<code>x/y</code>和<code>x/z</code>。
</li>
</ul>

注意：
<ul>
<li>
	当在<code>y.go</code>文件中引入一个引入路径均为<code>w/foo</code>的包的时候，被引入的包为<code>GOPATH/src/x/y/vendor/w/foo</code>文件夹中的包。
</li>
<li>
	当在<code>x.go</code>或者<code>z.go</code>文件中引入一个引入路径为<code>w/foo</code>的包的时候，被引入的包为<code>GOPATH/src/x/vendor/w/foo</code>文件夹中的包。
</li>
</ul>

<pre class="disable-line-numbers111"><code class="language-go">_ GOPATH
  |_ src
     |_ x
        |_ vendor
        |  |_ w
        |     |_ foo
        |        |_ foo.go    // package foo
        |_ y
        |  |_ vendor
        |  |  |_ w
        |  |     |_ foo
        |  |        |_ foo.go // package foo
        |  |_ y.go            // package y
        |_ z
        |  |_ z.go            // package z
        |_ x.go               // package x
</code></pre>

<p>
</p>

<p>
对于一个支持模块模式的代码包，其对应的<code>go.mod</code>文件中必须指定此<code>go.mod</code>文件所处的最内层文件夹对应的代码包的引入路径。
指定引入路径的格式为<code>module ImportPath</code>。
在这种模式下，只有和与此<code>go.mod</code>文件处于同一文件夹下的<code>vendor</code>文件夹才被视为特殊的文件夹。
并且请注意，如果<code>go</code>命令的选项<code>-mod=vendor</code>未提供，则此<code>vendor</code>文件夹的包将被忽略，而下载缓存（处于项目代码包之外）中相应的代码包将被使用。
</p>

在支持模块模式的情形下，假设有一个如下的包层级结构，则：

<ul>
<li>
	第一个<code>foo</code>包的引入路径均为<code>w/foo</code>。
	此包的父目录<code>vendor</code>文件夹被视为一个特殊的文件夹。
</li>
<li>
	另外一个<code>foo</code>包的引入路径均为<code>example.com/mypkg/x/y/vendor/w/foo</code>。
	注意这里的<code>vendor</code>文件夹被视为一个普通的包文件夹。
</li>
<li>
	<code>x</code>包、<code>y</code>包和<code>z</code>包的引入路径均分别为<code>example.com/mypkg/x</code>、<code>example.com/mypkg/x/y</code>和<code>example.com/mypkg/x/z</code>。
</li>
</ul>

注意：当在<code>x.go</code>、<code>y.go</code>或者<code>z.go</code>文件中引入一个引入路径均为<code>w/foo</code>的包的时候，被引入的包均为<code>MyProject/vendor/w/foo</code>文件夹中的包。

<pre class="disable-line-numbers111"><code class="language-go">_ MyProject
     |_ go.mod                // module example.com/mypkg
     |_ vendor
     |  |_ w
     |     |_ foo
     |        |_ foo.go       // package foo
     |_ x
        |_ y
        |  |_ vendor
        |  |  |_ w
        |  |     |_ foo
        |  |        |_ foo.go // package foo
        |  |_ y.go            // package y
        |_ z
        |  |_ z.go            // package z
        |_ x.go               // package x
</code></pre>

<p>
如果一个包文件夹含有一个<code>go.mod</code>文件，则请不要在它的任何（直接或间接）子包文件夹中也放置一个<code>go.mod</code>文件。
</p>

<p>
当一个代码包中的某个文件引入了另外一个代码包，则我们说前者代码包依赖于后者代码包。
</p>

<p>
Go不支持循环引用（依赖）。
如果一个代码包<code>a</code>依赖于代码包<code>b</code>，同时代码包<code>b</code>依赖于代码包<code>c</code>，则代码包<code>c</code>中的源文件不能引入代码包<code>a</code>和代码包<code>b</code>，代码包<code>b</code>中的源文件也不能引入代码包<code>a</code>。
</p>

<p>
当然，一个代码包中的源文件不能也没必要引入此代码包本身。
</p>

<p>
今后，我们称一个程序中含有<code>main</code>入口函数的名称为<code>main</code>的代码包为程序代码包，称其它代码包为库代码包。
一个程序只能有一个程序代码包。程序代码包不能被其它代码包引入。
</p>

<p>
代码包文件夹的名称并不要求一定要和其对应的代码包的名称相同。
但是，库代码包文件夹的名称最好设为和其对应的代码包的名称相同。
因为一个代码包的引入路径中包含的是此包的文件夹名，但是此包的默认引入名为此包的名称。
如果两者不一致，会使人感到困惑。
</p>

<p>
另一方面，最好给每个程序代码包指定一个有意义的名字，而不是它的包名<code>main</code>。
</p>
</div>

<a class="anchor" id="init"></a>
<h3><code>init</code>函数</h3>

<p>
在一个代码包中，甚至一个源文件中，可以声明若干名为<code>init</code>的函数。
这些<code>init</code>函数必须不带任何输入参数和返回结果。
</p>

<p>
注意，我们不能声明名为<code>init</code>的包级变量、常量或者类型。
</p>

<p>
在程序运行时刻，在进入<code>main</code>入口函数之前，每个<code>init</code>函数在此包加载的时候将被（串行）执行并且只执行一遍。
</p>

<div>
下面这个简单的程序中有两个<code>init</code>函数：

<pre class="line-numbers"><code class="language-go">package main

import "fmt"

func init() {
	fmt.Println("hi,", bob)
}

func main() {
	fmt.Println("bye")
}

func init() {
	fmt.Println("hello,", smith)
}

func titledName(who string) string {
	return "Mr. " + who
}

var bob, smith = titledName("Bob"), titledName("Smith")
</code></pre>

此程序的运行结果：
<pre class="output"><code>hi, Mr. Bob
hello, Mr. Smith
bye
</code></pre>

<p>
</p>

</div>

<a class="anchor" id="initialization-order"></a>
<h3>程序资源初始化顺序</h3>

<div>
<p>
一个程序中所涉及到的所有的在运行时刻要用到的代码包的加载是串行执行的。
在一个程序启动时，每个包中总是在它所有依赖的包都加载完成之后才开始加载。
程序代码包总是最后一个被加载的代码包。每个被用到的包会被而且仅会被加载一次。
</p>

<p>
在加载一个代码包的过程中，所有的声明在此包中的<code>init</code>函数将被串行调用并且仅调用执行一次。
一个代码包中声明的<code>init</code>函数的调用肯定晚于此代码包所依赖的代码包中声明的<code>init</code>函数。
所有的<code>init</code>函数都将在调用<code>main</code>入口函数之前被调用执行。
</p>

<p>
在同一个源文件中声明的<code>init</code>函数将按从上到下的顺序被调用执行。
对于声明在同一个包中的两个不同源文件中的两个<code>init</code>函数，Go语言白皮书推荐（但不强求）按照它们所处于的源文件的名称的词典序列（对英文来说，即字母顺序）来调用。
所以最好不要让声明在同一个包中的两个不同源文件中的两个<code>init</code>函数存在依赖关系。
</p>

<p>
在加载一个代码包的时候，此代码包中声明的所有包级变量都将在此包中的任何一个<code>init</code>函数执行之前初始化完毕。
</p>

在同一个包内，包级变量将尽量按照它们在代码中的出现顺序被初始化，但是一个包级变量的初始化肯定晚于它所依赖的其它包级变量。
比如，在下面的代码片段中，四个包级变量的初始化顺序依次为<code>y</code>、<code>z</code>、<code>x</code>、<code>w</code>。

<pre class="line-numbers"><code class="language-go">func f() int {
	return z + y
}

func g() int {
	return y/2
}

var (
	w       = x
	x, y, z = f(), 123, g()
)
</code></pre>

<p>
关于更具体的包级变量的初始化顺序，请阅读<a href="evaluation-orders.html#package-level-variables">表达式估值顺序规则</a>一文。
</p>

</div>

<h3>完整的引入声明语句形式</h3>

<div>

事实上，一个引入声明语句的完整形式为：

<pre class="disable-line-numbers111"><code class="language-go">import importname "path/to/package"
</code></pre>

<p>
其中引入名<code>importname</code>是可选的，它的默认值为被引入的包的包名（不是文件夹名）。
</p>

事实上，在本文上面的例子中的包引入声明中，<code>importname</code>部分都被省略掉了，因为它们都分别和引入的代码包的包名相同。
这些引入声明等价于下面这些：

<pre class="disable-line-numbers111"><code class="language-go">import fmt "fmt"        // <=> import "fmt"
import rand "math/rand" // <=> import "math/rand"
import time "time"      // <=> import "time"
</code></pre>

<p>
</p>

<p>
如果一个包引入声明中的<code>importname</code>没有省略，则限定标识符使用的前缀必须为<code>importname</code>，而不是被引入的包的名称。
</p>

<p>
引入声明语句的完整形式在日常编程中使用的频率不是很高。
但是在某些情况下，完整形式必须被使用。
比如，如果一个源文件引入的两个代码包的包名一样，为了防止使编译器产生困惑，我们至少需要用完整形式为其中一个包指定一个不同的引入名以区分这两个包。
</p>

下面是一个使用了完整引入声明语句形式的例子。

<pre class="line-numbers"><code class="language-go">package main

import (
	format "fmt"
	random "math/rand"
	"time"
)

func main() {
	random.Seed(time.Now().UnixNano())
	format.Print("一个随机数:", random.Uint32(), "\n")

	// 下面这两行编译不通过，因为rand不可识别。
	/*
	rand.Seed(time.Now().UnixNano())
	fmt.Print("一个随机数:", rand.Uint32(), "\n")
	*/
}
</code></pre>

一些解释：
<ul>
<li>
	我们必须使用<code>format</code>和<code>random</code>，而不是<code>fmt</code>和<code>rand</code>，来做为限定标识符的前缀。
</li>
<li>
	<code>Print</code>是<code>fmt</code>标准库包中的另外一个函数。
	和<code>Println</code>函数调用一样，一个<code>Print</code>函数调用也接受任意数量实参。
	它将逐个打印出每个实参的字符串表示形式。如果相邻的的两个实参都不是字符串类型，则在它们中间会打印一个空格字符。
</li>
</ul>

<p>
一个完整引入声明语句形式的引入名<code>importname</code>可以是一个句点(<code>.</code>)。
这样的引入称为句点引入。使用被句点引入的包中的导出资源时，限定标识符的前缀必须省略。
</p>

例子：
<pre class="line-numbers"><code class="language-go">package main

import (
	. "fmt"
	. "time"
)

func main() {
	Println("Current time:", Now())
}
</code></pre>

<p>
在上面这个例子中，<code>Println</code>和<code>Now</code>函数调用不需要带任何前缀。
</p>

<p>
一般来说，句点引入不推荐使用，因为它们的可读性较低。
</p>

<p>
一个完整引入声明语句形式的引入名<code>importname</code>可以是一个空标识符(<code>_</code>)。
这样的引入称为匿名引入。一个包被匿名引入的目的主要是为了加载这个包，从而使得这个包中的资源得以初始化。
被匿名引入的包中的<code>init</code>函数将被执行并且仅执行一遍。
</p>

在下面这个例子中，<a href="https://golang.google.cn/pkg/net/http/pprof/"><code>net/http/pprof</code>标准库包</a>中的所有<code>init</code>函数将在<code>main</code>入口函数开始执行之前全部执行一遍。

<pre class="line-numbers"><code class="language-go">package main

import _ "net/http/pprof"

func main() {
	... // 做一些事情
}
</code></pre>
</div>

<h3>每个非匿名引入必须至少被使用一次</h3>

<div>
除了匿名引入，其它引入必须在代码中被使用一次。
比如，下面的程序编译不通过。

<pre class="line-numbers"><code class="language-go">package main

import (
	"net/http" // error: 引入未被使用
	. "time"   // error: 引入未被使用
)

import (
	format "fmt"  // okay: 下面被使用了一次
	_ "math/rand" // okay: 匿名引入
)

func main() {
	format.Println() // 使用"fmt"包
}
</code></pre>

<p>
</p>

</div>
